package ch06
{
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.utils.getTimer;

	/**
	 * RK4 是数值积分中的老牌明星。如果人们提及“Runge-Kutta”， 几乎总是讨论 RK4。和
	   RK2 比，我们要做差不多类似的事情，只是不再采用开始和结束端的信息，而是要用 4 处的
	   信息。
	   在 RK4 中，求平均值的方式有点不同。先让我们看看伪码，很长，所以名词我都用了简
	   称。
	   // pos1 是对象的当前位置
	   // vel1 是对象的当前速度
	   acc1 = acceleration(pos1, vel1)
	   pos2 = pos1 + vel1 / 2 * time
	   vel2 = vel1 + acc1 / 2 * time
	   acc2 = acceleration(pos2, vel2)
	   pos3 = pos1 + vel2 / 2 * time
	   vel3 = vel1 + acc2 / 2 * time
	   acc3 = acceleration(pos3, vel3)
	   12
	   pos4 = pos1 + vel3 * time
	   vel4 = vel1 + acc3 * time
	   acc4 = acceleration(pos4, vel4)
	   pos1 += (vel1 + vel2 * 2 + vel3 * 2 + vel4) / 6 * time
	   vel1 += (acc1 + acc2 * 2 + acc3 * 2 + acc4) / 6 * time
	   注意，第一、第四步的求解和第二、第三步不同，在第二、第三步时候先除以了 2，在
	   最后求平均时又乘以了 2。这是考虑曲线末尾和中间有着特殊权重。也就是说，并不是平等
	   的加4次以后除以4，而 是多在中间加两次，然 后除以6.
	 */
	public class RK4 extends Sprite
	{
		private var _ball:Sprite;
		private var _position:Point;
		private var _velocity:Point;
		private var _gravity:Number = 32;
		private var _bounce:Number = -0.6;
		private var _oldTime:int;
		private var _pixelsPerFoot:Number = 10;
		private var radius:Number = 20;

		public function RK4()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;

			_ball = new Sprite();
			_ball.graphics.beginFill(0xff0000);
			_ball.graphics.drawCircle(0, 0, radius);
			_ball.graphics.endFill();
			_ball.x = 50;
			_ball.y = 50;
			addChild(_ball);

			_velocity = new Point(10, 0);
			_position = new Point(_ball.x / _pixelsPerFoot, _ball.y / _pixelsPerFoot);

			_oldTime = getTimer();
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}

		private function onEnterFrame(event:Event):void
		{
			var time:int = getTimer();
			var elapsed:Number = (time - _oldTime) / 1000;
			_oldTime = time;

			var accel1:Point = acceleration(_position, _velocity);

			var position2:Point = new Point();
			position2.x = _position.x + _velocity.x / 2 * elapsed;
			position2.y = _position.y + _velocity.y / 2 * elapsed;

			var velocity2:Point = new Point();
			velocity2.x = _velocity.x + accel1.x / 2 * elapsed;
			velocity2.y = _velocity.y + accel1.x / 2 * elapsed;

			var accel2:Point = acceleration(position2, velocity2);

			var position3:Point = new Point();
			position3.x = _position.x + velocity2.x / 2 * elapsed;
			position3.y = _position.y + velocity2.y / 2 * elapsed;

			var velocity3:Point = new Point();
			velocity3.x = _velocity.x + accel2.x / 2 * elapsed;
			velocity3.y = _velocity.y + accel2.y / 2 * elapsed;

			var accel3:Point = acceleration(position3, velocity3);

			var position4:Point = new Point();
			position4.x = _position.x + velocity3.x * elapsed;
			position4.y = _position.y + velocity3.y * elapsed;

			var velocity4:Point = new Point();
			velocity4.x = _velocity.x + accel3.x * elapsed;
			velocity4.y = _velocity.y + accel3.y * elapsed;

			var accel4:Point = acceleration(position4, velocity4);

			_position.x += (_velocity.x + 2 * velocity2.x + 2 * velocity3.x + velocity4.x) / 6 * elapsed;
			_position.y += (_velocity.y + 2 * velocity2.y + 2 * velocity3.y + velocity4.y) / 6 * elapsed;

			_velocity.x += (accel1.x + 2 * accel2.x + 2 * accel3.x + accel4.x) / 6 * elapsed;
			_velocity.y += (accel1.y + 2 * accel2.y + 2 * accel3.y + accel4.y) / 6 * elapsed;

			if (_position.y > (stage.stageHeight - radius) / _pixelsPerFoot)
			{
				_position.y = (stage.stageHeight - radius) / _pixelsPerFoot;
				_velocity.y *= _bounce;
			}
			if (_position.x > (stage.stageWidth - radius) / _pixelsPerFoot)
			{
				_position.x = (stage.stageWidth - radius) / _pixelsPerFoot;
				_velocity.x *= _bounce
			}
			else if (_position.x < radius / _pixelsPerFoot)
			{
				_position.x = radius / _pixelsPerFoot;
				_velocity.x *= _bounce;
			}

			_ball.x = _position.x * _pixelsPerFoot;
			_ball.y = _position.y * _pixelsPerFoot;
		}

		private function acceleration(p:Point, v:Point):Point
		{
			return new Point(0, _gravity);
		}
	}
}
